<?php

/**
 *
 * @author ben<lovebing365@qq.com>
 */
class HTTPClient {

    const METHOD_PUT = 'PUT';
    const METHOD_POST = 'POST';
    const METHOD_GET = 'GET';
    const METHOD_HEAD = 'HEAD';
    const METHOD_DELETE = 'DELETE';

    private $timeout = 0;
    private $header = array();
    private $responseHeaders = array();
    private static $instance = null;
    private $errno = 0;
    private $errstr = '';
    private $url = '';
    private $autoHost = true;
    private $cookie = array();
    private $isProxy = false;
    private $ch = null;
    private $followLocation = true;

    /**
     * 
     * @param int $timeout
     */
    public function __construct($timeout = 10) {
        $this->setTimeout($timeout);
        $this->ch = curl_init();
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->ch, CURLOPT_HEADER, true);
        curl_setopt($this->ch, CURLINFO_HEADER_OUT, true);
        curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
    }

    public function __destruct() {
        curl_close($this->ch);
    }

    /**
     * 
     * @param int $timeout
     * @return HTTPClient
     */
    public static function getInstance($timeout = 10) {
        if (is_null(self::$instance)) {
            self::$instance = new self($timeout);
        }
        return self::$instance;
    }

    /**
     * 
     * @param type $cert
     * @param type $certkey
     * @param type $certpwd
     */
    public function setSSLOpt($cert, $certkey, $certpwd, $rootca = null) {
        curl_setopt($this->ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($this->ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($this->ch, CURLOPT_SSLCERT, $cert);
        curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $certpwd);
        curl_setopt($this->ch, CURLOPT_SSLKEY, $certkey);

        if (!is_null($rootca)) {
            curl_setopt($this->ch, CURLOPT_CAINFO, $rootca);
        }
    }

    /**
     * 
     * @param string $name
     * @param string $value
     */
    public function setOneHeader($name, $value) {
        $this->header[$name] = $value;
    }

    /**
     * 
     * @param string $name
     * @param string $value
     */
    public function setOneCookie($name, $value) {
        $this->cookie[$name] = $value;
    }

    /**
     * 
     * @param boolean $flag
     */
    public function setFollowLocation($flag) {
        $this->followLocation = $flag;
    }

    /**
     * 
     * @param boolean $flag
     */
    public function setAutoHost($flag) {
        $this->autoHost = $flag ? true : false;
    }

    /**
     * 
     * @return array
     */
    public function getCookie() {
        return $this->cookie;
    }

    /**
     * 设置Content-Type
     * @param string $contentType
     * @return type
     */
    public function setContentType($contentType = null) {
        if (is_null($contentType)) {
            unset($this->header['Content-type']);
            return;
        }
        $this->header['Content-type'] = $contentType;
    }

    /**
     * cookie
     */
    private function buildCookie() {
        $arr = array();
        foreach ($this->cookie as $name => $value) {
            $arr[] = $name . '=' . $value;
        }
        $this->setOneHeader('Cookie', implode(';', $arr));
    }

    /**
     * header
     */
    public function buildHeader() {
        curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->getRequestHeader());
    }

    /**
     * 保存cookie
     */
    private function saveCookie() {
        $cookies = $this->getResponseHeaderItem('Set-Cookie');
        foreach ($cookies as $k => $v) {
            $arr = explode(';', $v);
            $str = $arr[0];

            $pos = strpos($str, '=');
            if ($pos !== false && $pos > 0) {
                $this->setOneCookie(substr($str, 0, $pos), substr($str, $pos + 1));
            }
        }
    }

    /**
     * 获取请求头
     * @return string
     */
    public function getRequestHeader() {
        $item = array();
        foreach ($this->header as $k => $v) {
            $item[] = $k . ': ' . $v;
        }
        return $item;
    }

    /**
     * 
     * @param string $type
     * @param string $content
     * @return resource
     */
    private function createContext($type = '', $content = '') {
        $ch = $this->ch;
        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, $this->followLocation);
        if (!$this->isProxy) {
            switch ($type) {
                case self::METHOD_POST:
                case self::METHOD_PUT:
                    $this->setContentType('application/x-www-form-urlencoded');
                    $this->setOneHeader('Content-Length', strlen($content));
                    break;
                default :
                    $this->setContentType(null);
            }
            if ($this->autoHost) {
                $this->setHostFromUrl();
            }
            $this->buildCookie();
        }
        $this->buildHeader();
        if ($content != '') {
            curl_setopt($this->ch, CURLOPT_POSTFIELDS, $content);
        }

        curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $type);
        return $ch;
    }

    /**
     * 设置HOST
     */
    public function setHostFromUrl() {
        $arr = parse_url($this->url);
        if (isset($arr['host'])) {
            $this->setOneHeader('Host', $arr['host']);
        }
    }

    /**
     * 
     * @param int $timeout
     */
    public function setTimeout($timeout) {
        $this->timeout = $timeout;
    }

    /**
     * 
     * @param type $url
     * @param array $data
     * @return type
     */
    public function get($url, array $data = array()) {
        if (!empty($data)) {
            $params = http_build_query($data);
            if (!strpos($url, '?')) {
                $url .= '?';
            }
            $url .= $params;
        }

        $this->url = $url;
        $context = $this->createContext(self::METHOD_GET);
        return $this->request($context);
    }

    /**
     * 
     * @param type $url
     * @param array $data
     * @return type
     */
    public function post($url, array $data) {
        $this->url = $url;
        $context = $this->createContext(self::METHOD_POST, http_build_query($data));
        return $this->request($context);
    }

    /**
     * 
     * @param type $url
     * @param type $xml
     * @return type
     */
    public function postXML($url, $xml) {
        return $this->postText($url, $xml);
    }

    /**
     * 
     * @param type $url
     * @param type $text
     * @return type
     */
    public function postText($url, $text) {
        $this->url = $url;
        $context = $this->createContext(self::METHOD_POST, $text);
        return $this->request($context);
    }

    /**
     * HEAD 方法
     * @param type $url
     */
    public function head($url) {
        $this->url = $url;
        $options = array(
            'http' => array(
                'header' => implode("\r\n", $this->getRequestHeader()),
                'method' => self::METHOD_HEAD,
                'timeout' => $this->timeout,
                'follow_location' => $this->followLocation
            )
        );
        $context = stream_context_create($options);
        file_get_contents($this->url, false, $context);
        $this->responseHeaders = $http_response_header;
    }

    /**
     * 
     * @return array
     */
    public function getResponseHeader() {
        return $this->responseHeaders;
    }

    /**
     * 
     * @param string $name
     * @return array
     */
    public function getResponseHeaderItem($name) {
        $headers = $this->getResponseHeader();
        $res = array();
        foreach ($headers as $item) {
            $pos = strpos($item, ':');
            if ($pos !== false && $pos > 0 && substr($item, 0, $pos) == $name) {
                $res[] = trim(substr($item, $pos + 1));
            }
        }
        return $res;
    }

    /**
     * 
     * @param resource $context 
     * @return string
     */
    private function request($context) {
        $this->setError(0, '');

        $res = curl_exec($context);
        if ($res === false) {
            $error = error_get_last();
            if (empty($error)) {
                $error = array(
                    'type' => 'curl_error',
                    'message' => curl_strerror(curl_errno($this->ch))
                );
            }
            $this->setError($error['type'], $error['message']);
            $this->responseHeaders = array();
            return false;
        }

        $headerSize = curl_getinfo($context, CURLINFO_HEADER_SIZE);
        $header = substr($res, 0, $headerSize - 4);
        $this->responseHeaders = explode("\r\n", $header);
        $this->saveCookie();
        return substr($res, $headerSize);
    }

    /**
     * 
     * @param int $errno
     * @param string $errstr
     */
    private function setError($errno = 0, $errstr = '') {
        $this->errno = $errno;
        $this->errstr = $errstr;
    }

    /**
     * 
     * @return int
     */
    public function getErrorCode() {
        return $this->errno;
    }

    /**
     * 
     * @return string
     */
    public function getErrorMessage() {
        return $this->errstr;
    }

    /**
     * 获取客户端请求头
     * @return array
     */
    public function getAllClientheaders() {
        $h = array('CONTENT_LENGTH', 'CONTENT_TYPE');
        $headers = array();
        foreach ($_SERVER as $name => $value) {
            if (in_array($name, $h)) {
                $headers[str_replace('_', '-', ucwords(strtolower($name)))] = $value;
                continue;
            }
            if (substr($name, 0, 5) == 'HTTP_') {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
        return $headers;
    }

    /**
     * 代理
     * @param string $url
     * @param string $cookieString
     * @return string
     */
    public function proxy($url, $cookieString = '') {
        $this->url = $url;
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';

        $this->isProxy = true;
        $headers = $this->getAllClientheaders();
        if ($cookieString != '') {
            $headers['Cookie'] = $cookieString;
        }
        $urlInfo = parse_url($url);
        $headers['Referer'] = $urlInfo['scheme'] . '://' . $urlInfo['host'];
        $this->header = $headers;
        $this->setHostFromUrl();

        switch ($method) {
            case 'POST':
            case 'PUT':
                $data = file_get_contents('php://input');
                $context = $this->createContext($method, $data);
                break;
            default :
                $context = $this->createContext($method);
        }
        $res = $this->request($context);

        $resHeaders = $this->getResponseHeader();
        foreach ($resHeaders as $item) {
            header($item);
        }
        return $res;
    }

    /**
     * 
     * @param array $nodes
     * @param type $saveDir
     * @param type $timeout
     * @return type
     */
    public static function multiDownload(array $nodes, $saveDir, $timeout = 10) {
        if (empty($nodes)) {
            return;
        }
        if (!is_dir($saveDir)) {
            mkdir($saveDir);
        }
        $mh = curl_multi_init();
        $curlArray = array();
        foreach ($nodes as $item) {
            $ch = curl_init();
            $curlArray[] = array(
                'name' => $item['name'],
                'ch' => $ch
            );
            curl_setopt($ch, CURLOPT_URL, $item['url']);
            curl_setopt($ch, CURLOPT_HEADER, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_multi_add_handle($mh, $ch);
        }
        $active = null;
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);

        while ($active && $mrc == CURLM_OK) {
            if (curl_multi_select($mh) != -1) {
                do {
                    $mrc = curl_multi_exec($mh, $active);
                } while ($mrc == CURLM_CALL_MULTI_PERFORM);
            }
        }
        foreach ($curlArray as $item) {
            $path = $item['name'];
            file_put_contents($saveDir . '/' . $path, curl_multi_getcontent($item['ch']));
            curl_multi_remove_handle($mh, $item['ch']);
            curl_close($item['ch']);
        }
        curl_multi_close($mh);
    }

    public function getFileNameFromDisposition() {
        $value = $this->getResponseHeaderItem('Content-disposition');
        if (empty($value)) {
            return false;
        }
        $disposition = $value[0];

        $pos = strpos($disposition, ';');
        $fileinfo = substr($disposition, $pos + 1);

        $pos2 = strpos($fileinfo, '=');
        $file = substr($fileinfo, $pos2 + 1);

        if (strpos($file, '"') === 0) {
            $file = substr($file, 1);
        }
        $len = strlen($file);
        if (strpos($file, '"') === $len - 1) {
            $file = substr($file, 0, $len - 1);
        }
        return $file;
    }
}
